﻿/*
 * 原始代码从 http://www.jeremyskinner.co.uk/2011/01/24/strongly-typed-data-access-with-webmatrix-data/ 拷贝
 * 
 * 日期：2011-7-14
 * 作者：minco
 * */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WebMatrix.Data;
using System.Linq.Expressions;
using System.Reflection;
using System.ComponentModel;
using System.Globalization;

namespace WebMatrix.Data
{
    /// <summary>
    /// 将类的属性映射出来，以实现动态设置类的实例值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Mapper<T>
    {
        private Func<T> factory;
        private Dictionary<string, Action<T, object>> setters = new Dictionary<string, Action<T, object>>();
        private static Lazy<Mapper<T>> _instance = new Lazy<Mapper<T>>(() => new Mapper<T>());

        public static Mapper<T> Create()
        {
            return _instance.Value;
        }

        private Mapper()
        {
            factory = CreateActivatorDelegate();

            foreach (var property in typeof(T).GetProperties())
            {
                if (property.CanWrite)
                {
                    //如果提供了列映射，则使用列映射的字段名称
                    //var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                    //if (attributes != null && attributes.Length == 1)
                    //{
                    //    var column = (ColumnAttribute)attributes[0];
                    //    if (column.Name != null)
                    //    {
                    //        setters[column.Name] = BuildSetterDelegate(property);
                    //    }
                    //    else
                    //    {
                    //        setters[property.Name] = BuildSetterDelegate(property);
                    //    }
                    //}
                    //else
                    //{
                        setters[property.Name] = BuildSetterDelegate(property);
                    //}
                }
            }
        }

        public T Map(DynamicRecord record)
        {
            var instance = factory();

            foreach (var column in record.Columns)
            {
                var obj = record[column];
                Action<T, object> setter;               
                //if (obj != null && setters.TryGetValue(column, out setter))
                if (obj != null && setters.TryGetValue(column, StringComparison.InvariantCultureIgnoreCase, out setter))
                {
                    try
                    {
                        setter(instance, obj);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(column + "列映射出错，record[column]="+obj, ex);
                    }
                    
                }
            }

            return instance;
        }


        private static Func<T> CreateActivatorDelegate()
        {
            return CreateActivatorDelegate(typeof(T).GetConstructor(Type.EmptyTypes));
        }

        private static Func<T> CreateActivatorDelegate(ConstructorInfo constructor)
        {
            return Expression.Lambda<Func<T>>(Expression.New(constructor)).Compile();
        }

        private static Action<T, object> BuildSetterDelegate(PropertyInfo prop)
        {
            var instance = Expression.Parameter(typeof(T), "x");
            var argument = Expression.Parameter(typeof(object), "v");

            //Expression setterCall = null;
            //if(prop.PropertyType == typeof(bool))
            //{
            //    //因mysql bit类型映射城ulong类型，故做个判断并转型。
            //    //因Mono的TypeConvert实现与.Net不同，故不能使用TypeConvert进行转换
            //    var boolTypeExpression = Expression.Constant(typeof(bool));
            //    var zeroExpression = Expression.Constant((ulong)0);
            //    var iifCondition = Expression.Condition(Expression.TypeEqual(argument, typeof(ulong)), 
            //        Expression.MakeBinary(ExpressionType.NotEqual, Expression.Convert(argument, typeof(ulong)), zeroExpression), 
            //        Expression.Convert(argument, prop.PropertyType));

            //    setterCall = Expression.Call(
            //    instance,
            //    prop.GetSetMethod(true),
            //    iifCondition);
            //}
            //else
            //{
                var setterCall = Expression.Call(
                instance,
                prop.GetSetMethod(true),
                Expression.Convert(argument, prop.PropertyType));
            //}

            return (Action<T, object>)Expression.Lambda(setterCall, instance, argument).Compile();
        }
    }
}
